/*
 * These files are distributed under the terms of the GNU GPLv2, the GNU General
 * Public version 2. See LICENSE.txt
 */
package fileCommodore.view;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;

import javax.swing.*;
import javax.swing.event.*;

import org.apache.commons.vfs.*;
import org.jdesktop.swingx.decorator.*;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.renderer.*;

import fileCommodore.*;
import fileCommodore.model.*;
import fileCommodore.plugin.*;
import fileCommodore.plugin.pathHopper.*;

/**
 * @author nado18@gmail.com, on Jul 17, 2007
 */
public class FilePane extends JPanel
{
	private JLabel tfFilter = new JLabel();
	private PatternFilter filter;
	private FileObjectTableModel model;
	private MyTable table;
	private int fontSize = 13;
	private boolean active = false;
	
	private static final int ICON = 0;
	private static final int FILENAME = 1;
	private static final int SIZE = 2;
	
	private Main frame;
	private PathHopper hopper;
	
	public FilePane(Main frame, PathHopper pathHopper,
		FileObjectTableModel model)
	{
		this.frame = frame;
		this.hopper = pathHopper;
		this.model = model;
		
		createTable();
		
		JScrollPane spTable = new JScrollPane( table );
		
		setLayout( new BorderLayout() );
		add( hopper, BorderLayout.NORTH );
		add( spTable, BorderLayout.CENTER );
		add( tfFilter, BorderLayout.SOUTH );
		
		tfFilter.setFocusTraversalKeysEnabled( false );
		listen();
		
		hopper.repaint();
	}
	
	public FileObjectTableModel getModel()
	{
		return model;
	}
	
	private void createTable()
	{
		table = new MyTable( model );
		table.setColumnControlVisible( true );
		table.setShowHorizontalLines( false );
		table.setShowVerticalLines( false );
		table.setRowMargin( 0 );
		table.setRolloverEnabled( true );
		table.setHighlighters( new ConditionalHighlighter( Color.yellow,
				SystemColor.BLACK, 0, -1 )
		{
			@Override
			protected boolean test( ComponentAdapter arg0 )
			{
				FileRowItem item =
						(FileRowItem) arg0.getValueAt( arg0.row, FILENAME );
				if (null != item)
				// TODO: Determine FilePane.reload can be implemented such
				// as to not need this check
				{
					return item.isTagged();
				}
				else
				{
					return false;
				}
			}
		} );
		table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
		table.getColumnModel().setColumnMargin( 0 );
		filter = new PatternFilter( "", 0, FILENAME );
		FilterPipeline fp = new FilterPipeline( new Filter[]
			{
				filter
			} );
		table.setFilters( fp );
		
		table.getColumnModel().getColumn( ICON ).setCellRenderer(
			new DefaultTableRenderer( new MyIconProvider( model ) ) );
		table.getColumnModel().getColumn( FILENAME ).setCellRenderer(
			new DefaultTableRenderer( new MyFilenameProvider( model ) ) );
		table.getColumnModel().getColumn( SIZE ).setCellRenderer(
			new DefaultTableRenderer( new MySizeProvider( model ) ) );
		
		table.getColumnModel().getColumn( ICON ).setMaxWidth( 2 * fontSize );
		table.getColumnModel().getColumn( SIZE ).setMaxWidth( 10 * fontSize );
	}
	
	private void setFilter( String aFilter )
	{
		try
		{
			int selectedIndex = table.getSelectedRow();
			FileRowItem selectedItem =
				selectedIndex >= 0
					? (FileRowItem) table.getValueAt(selectedIndex, FILENAME)
					: null
				;
			
			tfFilter.setText( aFilter );
			
			filter.setPattern( Pattern.compile(
				aFilter,
				Pattern.CASE_INSENSITIVE ) );
			
			tfFilter.setForeground( SystemColor.textText );
			
			if( null != selectedItem && table.getRowCount() > 0 )
			{
				selectedIndex = 0;
				
				for(int row=0; row<table.getRowCount(); row++)
				{
					FileRowItem item = (FileRowItem) table.getValueAt(row, FILENAME);
					if( selectedItem.equals(item) )
					{
						selectedIndex = row;
						break;
					}
				}
				
				if( table.getRowCount() > 0 )
				{
					table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex);
				}
			}
		}
		catch (PatternSyntaxException ex)
		{
			tfFilter.setForeground( SystemColor.red );
		}
	}
	
	private boolean isUsable( char c )
	{
		boolean retval = ( c >= 32 ) && ( c < 128 );
		
		return retval;
	}
	
	private void listen()
	{
		model.addPathChangeListener( new PathChangeListener()
		{
			public void pathChanged()
			{
				setFilter( "" );
				if (table.getRowCount() > 0)
				{
					table.getSelectionModel().setSelectionInterval( 0, 0 );
				}
				notifySelection();
				focus();
			}
		} );
		
		table.getSelectionModel().addListSelectionListener(
			new ListSelectionListener()
			{
				public void valueChanged( ListSelectionEvent e )
				{
					notifySelection();
				}
			} );
		
		table.addFocusListener( new FocusAdapter()
		{
			@Override
			public void focusGained( FocusEvent e )
			{
				super.focusGained( e );
				focus();
			}
		} );
		
		tfFilter.addKeyListener( new KeyAdapter()
		{
			@Override
			public void keyPressed( KeyEvent e )
			{
				if (KeyEvent.VK_BACK_SPACE == e.getKeyCode())
				{
					if (0==getFilter().length())
					{
						changeToParent();
					}
					else
					{
						String f = getFilter();
						int len = f.length();
						setFilter( f.substring( 0, len - 1 ) );
					}
					destroy( e );
				}
				
				if (e.getKeyCode() == KeyEvent.VK_ESCAPE)
				{
					setFilter( "" );
				}
				
				if (KeyEvent.VK_DOWN == e.getKeyCode()
						|| KeyEvent.VK_UP == e.getKeyCode()
						|| KeyEvent.VK_PAGE_DOWN == e.getKeyCode()
						|| KeyEvent.VK_PAGE_UP == e.getKeyCode()
						|| KeyEvent.VK_HOME == e.getKeyCode()
						|| KeyEvent.VK_END == e.getKeyCode())
				{
					table.handleKeyEvent( e );
					destroy( e );
				}
				
				if (KeyEvent.VK_SPACE == e.getKeyCode())
				{
					int[] rows = table.getSelectedRows();
					for (int row : rows)
					{
						FileRowItem item =
								model.getItemAt( table.convertRowIndexToModel( row ) );
						if (null != item)
						{
							item.toggleTag();
						}
					}
					
					int next =
							Math.min(
								1 + table.getSelectionModel().getMaxSelectionIndex(),
								table.getRowCount() - 1 );
					table.getSelectionModel().setSelectionInterval( next, next );
					
					destroy( e );
				}
				
				if (KeyEvent.VK_ENTER == e.getKeyCode())
				{
					launchBunch();
					destroy( e );
				}
				
				if (KeyEvent.VK_TAB == e.getKeyCode())
				{
					switchPanel();
					destroy( e );
				}
				
				frame.notifyPlugins( e );
				
				if (!e.isConsumed() && !e.isControlDown() && !e.isAltDown()
						&& !e.isAltGraphDown() && !e.isMetaDown()
						&& isUsable( e.getKeyChar() ))
				{
					setFilter( getFilter() + e.getKeyChar() );
				}
			}
		} );
		
		table.addMouseListener( new MouseAdapter()
		{
			@Override
			public void mouseClicked( MouseEvent e )
			{
				super.mouseClicked( e );
				
				if (2 == e.getClickCount())
				{
					Point clickAt = e.getPoint();
					int row = table.rowAtPoint( clickAt );
					FileRowItem item =
							model.getItemAt( table.convertRowIndexToModel( row ) );
					if (null != item)
					{
						FileObject fo = item.getFileObject();
						try
						{
							if (FileType.FOLDER.equals( fo.getType() ))
							{
								model.changeTo( fo );
							}
							else if (FileType.FILE.equals( fo.getType() ))
							{
								try
								{
									open( fo );
								}
								catch (IOException e1)
								{
									JOptionPane.showMessageDialog(
										frame,
										"Could not open file\r\n\r\n" + e1,
										"Open Error",
										JOptionPane.ERROR_MESSAGE );
									// TODO Auto-generated catch block
									e1.printStackTrace();
								}
							}
						}
						catch (FileSystemException e1)
						{
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}
					}
				}
			}
		} );
	}
	
	protected String getFilter()
	{
		return tfFilter.getText();
	}
	
	private void open( FileObject fo ) throws IOException
	{
		File f = new File( fo.getName().getPath() );
		
		// TODO: Issue 6: Use Desktop.getDesktop().open( f ) when Java 6 is standard on the Mac
		Plugin.launchProcessWithoutTerminal(
			f.getParentFile(),
			new String[] {
				f.getAbsolutePath()
			}
		);
	}
	
	private Vector<FileRowItem> getSelectedItems()
	{
		Vector<FileRowItem> retval = new Vector<FileRowItem>();
		
		int[] selectedIndices = table.getSelectedRows();
		for (int i = 0; i < selectedIndices.length; i++)
		{
			int row = table.convertRowIndexToModel( selectedIndices[i] );
			retval.add( model.getItemAt( row ) );
		}
		
		return retval;
	}
	
	private void launchBunch()
	{
		Vector<FileRowItem> set = getSelectedItems();
		
		Vector<FileRowItem> dirs = new Vector<FileRowItem>();
		
		for (FileRowItem item : set)
		{
			if (item.isDirectory())
			{
				dirs.add( item );
			}
			else
			{
				try
				{
					open( item.getFileObject() );
				}
				catch (IOException e)
				{
					JOptionPane.showMessageDialog(
						frame,
						"Could not open file\r\n\r\n" + e,
						"Open Error",
						JOptionPane.ERROR_MESSAGE );
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		if (1 == dirs.size())
		{
			try
			{
				model.changeTo( dirs.firstElement().getFileObject() );
			}
			catch (FileSystemException e)
			{
				JOptionPane.showMessageDialog( frame, String.format(
					"Could not navigate to given directory: %s.\r\n\r\n",
					dirs.firstElement().getFileName(),
					e ), "Navigation Error", JOptionPane.ERROR_MESSAGE );
			}
		}
	}
	
	protected void changeToParent()
	{
		try
		{
			if (null != getCurrent())
			{
				FileObject parent = getCurrent().getParent();
				model.changeTo( parent );
			}
		}
		catch (FileSystemException e)
		{
			JOptionPane.showMessageDialog( frame, String.format(
				"Could not navigate to parent directory.\r\n\r\n",
				e ), "Navigation Error", JOptionPane.ERROR_MESSAGE );
		}
	}
	
	public void focus()
	{
		tfFilter.requestFocus();
	}
	
	private void destroy( KeyEvent ev )
	{
		ev.consume();
		ev.setKeyChar( (char) -1 );
		ev.setKeyCode( -1 );
	}
	
	private void switchPanel()
	{
		frame.switchPanel( this );
	}
	
	private void dim()
	{
		hopper.dim();
	}
	
	private void shine()
	{
		hopper.shine();
	}
	
	public Selection getSelection()
	{
		Selection sel = new Selection();
		sel.setCurrent( new FileRowItem( getCurrent() ) );
		int selectedRow = table.getSelectedRow();
		for (int i = 0; i < table.getRowCount(); i++)
		{
			int row = table.convertRowIndexToModel( i );
			FileRowItem item = model.getItemAt( row );
			if (i == selectedRow)
			{
				sel.setSelected( item );
			}
			else
			{
				sel.addUnelected( item );
			}
		}
		
		return sel;
	}
	
	private void notifySelection()
	{
		frame.notifySelection( this );
	}
	
	public boolean isActive()
	{
		return active;
	}
	
	public void setActive( boolean active )
	{
		this.active = active;
		
		if (isActive())
		{
			shine();
		}
		else
		{
			dim();
		}
	}
	
	private FileObject getCurrent()
	{
		return model.getCurrent();
	}
}
